package com.ptppay.cf.starter;

import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.apache.log4j.Logger;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.listeners.OnTransactionBroadcastListener;
import org.bitcoinj.net.discovery.DnsDiscovery;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.SPVBlockStore;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.ptppay.cf.common.CommonEnums;
import com.ptppay.cf.core.business.service.CFService;
import com.ptppay.cf.domain.CoinPrice;
import com.ptppay.cf.processor.mq.MqPublisher;

import cn.finder.httpcommons.utils.JsonUtils;
import cn.finder.mq.router.DefaultTopicRouterMqttClient;
import cn.finder.wae.common.comm.AppApplicationContextUtil;
import cn.finder.wae.helper.EmailHelper;

/***
 * Btc Chain
 * 
 * @author finder
 *
 * 
 *
 */


public class BtcListenerServlet extends HttpServlet {

	private static Logger logger = Logger.getLogger(BtcListenerServlet.class);

	/**
	 * 
	 */
	private static final long serialVersionUID = -7958000580578197415L;

	
	private int LEAST_CONFIRMS=1; // 1 confirms
	
	CFService cFService;
	DefaultTopicRouterMqttClient mqttClient;
	
	
	@Override
	public void init(ServletConfig servletConfig) throws ServletException {

		String enabled = servletConfig.getInitParameter("enabled");

		if ("true".equalsIgnoreCase(enabled)) {
			logger.info("BtcListener.init  starting....  ");

		}


		cFService =AppApplicationContextUtil.getContext().getBean("cFService", CFService.class);
		
		mqttClient =AppApplicationContextUtil.getContext().getBean("finderMqttClient", DefaultTopicRouterMqttClient.class);
		
		
		
		
		new BitCoinReceiver().start();
		
		
		 /**
		     * Create an Observable to emit pending transactions, i.e. those transactions that have been
		     * submitted by a node, but don't yet form part of a block (haven't been mined yet).
		     *  一个新的交易被某个节点提交； 还没有被打包
		     *
		     * @return Observable to emit pending transaction hashes.
		     */
		/* pendingTransactionHash_sub = web3.ethPendingTransactionHashObservable().subscribe(txHash -> {
			 logger.info(new Date());
			 logger.info("====ethPendingTransactionHashObservable: txHash:"+txHash);
			 
			 
			 try{
				 
				 EthTransaction ethTx= EthChain.getTransactionInfo(txHash);
				 Transaction tx= ethTx.getResult();
				 // 买币取现
				 String to=tx.getTo(); //平台账户
				 
				 //BigInteger height= tx.getBlockNumber(); // block number
				 
				 BigInteger value=tx.getValue(); // coin amt
				 
				 //find  address <=> atm,if exists, do cash out ,else ignore
				 
				 Map<String,Object> bindItem= cFService.findBindingByWalletAddress(tx.getFrom());
				 if(bindItem!=null){
					 
					 
					 
					 //计算金额 
					 
					BigInteger tx_fee= tx.getGas().multiply(tx.getGasPrice());//0.946
					
					String eqt_id=bindItem.get("eqt_id").toString();
					
					//compare to pretx
					Map<String,Object> pretx=cFService.findLatestingPreTxInfo(eqt_id, to, CommonEnums.PRETXS_BUY_SELL_SELL.getValue());
					
					if(pretx!=null){
						//tx is exists
						
						//update chain tx to pretx
						
						String pretx_id=pretx.get("id").toString();
						
						cFService.updatePreTxByChainTx(pretx_id, tx.getHash(), tx.getValue().divide(new BigInteger("1000000000000000000")).doubleValue()+"");
						
						//save chain txinfo
						
						saveTxInfo(tx);
						
						// send out cash command
						
						String eqt_code=cFService.findEquipmentCode(eqt_id);
						
						
						//ETH:RMB=1:5000
						//double exchange_rate=Double.parseDouble(cFService.findEqtParam(eqt_code, "lab").get("valuestr").toString());
						
						CoinPrice coinPrice=cFService.findCoinPrice(eqt_code, CommonEnums.COIN_TYPE_LAB.getValue());
						double exchange_rate=coinPrice.getBuy_price();// receive coin price
						
						//出钞金额 (元)
						
						Double money_amt=tx.getValue().divide(new BigInteger("1000000000000000000")).doubleValue()*exchange_rate;
						//去整数
						int money=money_amt.intValue();
						
						Map<String,Object> returnMap=new HashMap<String,Object>();
						returnMap.put("eqt_code", eqt_code);
						returnMap.put("pre_tx_id", pretx_id);
						returnMap.put("money_amt", money);
						
						// 设备里面缺钱 应该也需要通知设备
						
						
						String returnData=JsonUtils.getJsonString4JavaPOJO(returnMap);
						String topic=MqPublisher.TOPIC_CF_OUTCASH;
						try {
							//mqttClient.publishMsgOne(topic, returnData, eqt_code);
							MqPublisher.publish(topic, returnMap, eqt_code);
							
						} catch (Throwable e) {
							
							//记录到日志 ，并且发邮件
							new EmailHelper().sendEmailToAdminInThread(String.format(" MQ publish exception .topic: %s,payload:%,to_client_id:%s, exception detail:%s", topic,returnData,eqt_code,e.toString()), e.toString());
							
						} 
						
					}
					
					
					
				 }
				 
				 
			 }
			catch(Throwable e){
				 logger.error(e);
				 new EmailHelper().sendEmailToAdminInThread(String.format(" ethPendingTransactionHashObservable txHash: [%s] 异常:", txHash), e.toString());
			}
			 finally{
				 
				 
			 }
		});
		 */

		
	}

	@Override
	public void destroy() {
	}
	
	
	/***
	 * save chain tx , not need. we can find tx detail to block info view
	 * @param trans
	 */
	/*private void saveTxInfo(Map<String,Object>){
		
		Map<String,Object> txInfo=new HashMap<>();
		txInfo.put("tx_hash", trans.getHash());
		txInfo.put("from_address", trans.getFrom());
		txInfo.put("to_address", trans.getTo());
		//txInfo.put("amt", trans.getValue());
		//txInfo.put("block_number", trans.getBlockNumber());
		txInfo.put("block_number", "");
		//txInfo.put("creates", trans.getCreates());
		txInfo.put("creates", "");
		txInfo.put("gas", trans.getGas());
		txInfo.put("gas_price", trans.getGasPrice());
		txInfo.put("gas_price_raw", trans.getGasPriceRaw());
		txInfo.put("gas_raw", trans.getGasRaw());
		txInfo.put("input", trans.getInput());
		txInfo.put("nonce", trans.getNonce());
		txInfo.put("nonce_raw", trans.getNonceRaw());
		//txInfo.put("publick_key", trans.getPublicKey());
		txInfo.put("publick_key", "");
		txInfo.put("r", trans.getR());
		//txInfo.put("raw", trans.getRaw());
		txInfo.put("raw", "");
		txInfo.put("s", trans.getS());
		txInfo.put("transaction_index", trans.getTransactionIndex());
		txInfo.put("value", trans.getValue());
		txInfo.put("value_raw", trans.getValueRaw());
		txInfo.put("token_type", CommonEnums.LABOR_COIN_TYPE.getValue());
		txInfo.put("tx_fee", trans.getGas().multiply(trans.getGasPrice()));
		
		txInfo.put("has_error", eth_trans.hasError()?"1":"0");
		if(eth_trans.hasError()){
			txInfo.put("error_code", eth_trans.getError().getCode());
			txInfo.put("error_data", eth_trans.getError().getData());
			txInfo.put("error_message", eth_trans.getError().getMessage());
		}
		txInfo.put("status", CommonEnums.TX_STATUS_COMPLETE.getValue());
		
		cFService.saveTxInfo(txInfo);
	}*/
	
	public static class BitCoinReceiver extends Thread implements WalletCoinsReceivedEventListener,OnTransactionBroadcastListener {
		
		NetworkParameters params = TestNet3Params.get();
		
		CFService cFService;
		public BitCoinReceiver(){
			
			cFService =AppApplicationContextUtil.getContext().getBean("cFService", CFService.class);
		}
		
		@Override
		public void run() {
			try {
				init();

				logger.info("Waiting for coins...");

				while (true) {
					Thread.sleep(20);
				}
			} catch (BlockStoreException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}catch(UnreadableWalletException e){
				e.printStackTrace();
			}
		}

		private void init() throws BlockStoreException, UnreadableWalletException {
			
			  BriefLogFormatter.initWithSilentBitcoinJ();
			// 加载ECKey list
			
			//ECKey key = new ECKey(); // 创建一个比特币的地址
			
			//logger.info("We created a new key:\n" + key);
			
			//Address addressFromKey = key.toAddress(params);
			//logger.info("Public Address generated: " + addressFromKey);
			//wallet.addWatchedAddress(add);
			//logger.info("Private key is: " + key.getPrivateKeyEncoded(params).toString());

		//	Wallet wallet = new Wallet(params);
			//wallet.importKey(key); // 导入钱包中
			
			//WalletExtension w=new StoredPay
			//Wallet.loadFromFile(new File("D:\\ws\\java\\wallet-template\\WalletTemplate-test"), WalletExtension.)
			/*String seedCode = "noodle wrist fancy cram garbage rose very expose buyer two cloud noise";
			long creationtime = new Date().getTime();
			DeterministicSeed seed = new DeterministicSeed(seedCode, null, "", creationtime);
			Wallet wallet =Wallet.fromSeed(params, seed);
			
			logger.info("create wallet balance:" + wallet.getBalance().value);*/
			
			//System.loadLibrary("secp256k1");
			Wallet wallet=new Wallet(params);//spent more time
			
			List<Map<String,Object>> saleCoinWallets= cFService.findCoinSupportWalletInfo(CommonEnums.COIN_TYPE_BTCT.getValue(),CommonEnums.PRETXS_BUY_SELL_SELL.getValue());
			
			List<ECKey> eckeys=new ArrayList<ECKey>();
			for(Map<String,Object> saleCoinWalletItem:saleCoinWallets){
				
				String private_key=saleCoinWalletItem.get("private_key").toString();
				
				ECKey key = ECKey.fromPrivate(new BigInteger(private_key));
				eckeys.add(key);
				
				System.out.println("wallet address: "+key.toAddress(params));
			}
			wallet.importKeys(eckeys);
			
			
			File blockFile = new File("d:\\cf-bitcoin-blocks");
			SPVBlockStore blockStore = new SPVBlockStore(params, blockFile);

			BlockChain blockChain = new BlockChain(params, blockStore);
			
			// if use proxy
			/*System.setProperty("socksProxyHost", "127.0.0.1");
			System.setProperty("socksProxyPort", "9050");
			peerGroup = new PeerGroup(params, chain, new BlockingClientManager());*/
			
			PeerGroup peerGroup = new PeerGroup(params, blockChain);
			peerGroup.addPeerDiscovery(new DnsDiscovery(params));
			
			
			peerGroup.addWallet(wallet); //这里可以动态添加 需要管理的钱包权限

			logger.info("Start peer group");
			peerGroup.start();
			
			//ListenableFuture<?> a = peerGroup.startAsync();
			
			logger.info("Downloading block chain");
			
			peerGroup.downloadBlockChain(); //choke
		
			
			
			
			
			logger.info("Block chain downloaded");

			wallet.addCoinsReceivedEventListener(this);

			//
			peerGroup.addOnTransactionBroadcastListener(this);

			logger.info("block height: "+peerGroup.getMostCommonChainHeight());
		}

		
		@Override
		public void onTransaction(Peer peer, Transaction tx) {
			// TODO Auto-generated method stub

			logger.info("============receive broadcast=========");
			logger.info(tx);
			
			logger.info(" number of broadcast peers:"+tx.getConfidence().numBroadcastPeers());
			logger.info(" DepthInBlocks:"+tx.getConfidence().getDepthInBlocks());
			logger.info(" confidence type:"+ tx.getConfidence().getConfidenceType());
			
			
			logger.info("============inputs=========");
			
			logger.info(tx.getInputs().size() +" INPUTS");
			

			logger.info("============outputs=========");

			logger.info(tx.getOutputs());

			logger.info("============output detail=========");
			// UTXO
			List<TransactionOutput> tx_outs = tx.getOutputs();

			for (TransactionOutput out_item : tx_outs) {

				Address address = null;
				try {
					if(out_item.getScriptPubKey().getScriptType()==Script.ScriptType.P2SH){
						
						//P2SH: back to sender address  (pay-to-script-hash)
						address = out_item.getAddressFromP2SH(params);
						
						logger.info("P2SH=>"+address.toBase58() + "(" + out_item.getValue().toFriendlyString() + ")");
						
						
					}else if(out_item.getScriptPubKey().getScriptType()==Script.ScriptType.P2PKH){
						
						//P2PK receiver address coin number 
						address =out_item.getAddressFromP2PKHScript(params);
						
						logger.info(address.toBase58() + "(" + out_item.getValue().toFriendlyString() + ")");
						
						double value=out_item.getValue().longValue()/100000000.0;
						
						String to=address.toBase58();
						// search bind equipment
						
						
					    Map<String,Object>	bindItem=cFService.findBindingByWalletAddressByMchAddress(to);
						//
						 if(bindItem!=null){
							 
							 
							 //计算金额 
							// BigInteger tx_fee;
						//	BigInteger tx_fee= tx.getGas().multiply(tx.getGasPrice());//0.946
							
							String eqt_id=bindItem.get("eqt_id").toString();
							
							//compare to pretx
							Map<String,Object> pretx=cFService.findLatestingPreTxInfo(eqt_id, to, CommonEnums.PRETXS_BUY_SELL_SELL.getValue());
							
							if(pretx!=null){
								//tx is exists
								
								//update chain tx to pretx
								
								String pretx_id=pretx.get("id").toString();
								
								cFService.updatePreTxByChainTx(pretx_id, tx.getHash().toString(), value+"");
								
								//save chain txinfo
								
								// not need
								//saveTxInfo(tx);
								
								// send out cash command
								
								String eqt_code=cFService.findEquipmentCode(eqt_id);
								
								
								//BTC:RMB=1:5000
								//double exchange_rate=Double.parseDouble(cFService.findEqtParam(eqt_code, "lab").get("valuestr").toString());
								String coin_type="";
								if(to.startsWith("1")){
					    			   // btc mainnet
					    			   coin_type=CommonEnums.COIN_TYPE_BTC.getValue();
					    			} else if(to.startsWith("2") || to.startsWith("m") ||to.startsWith("n")){
					    			   // btc testnet
					    			   coin_type=CommonEnums.COIN_TYPE_BTCT.getValue();
					    			}
								
								CoinPrice coinPrice=cFService.findCoinPrice(eqt_code, coin_type);
								double exchange_rate=coinPrice.getSell_price();// receive coin price
								
								//出钞金额 (元)
								
								Double money_amt=value*exchange_rate;
								//去整数
								int money=money_amt.intValue();
								
								Map<String,Object> returnMap=new HashMap<String,Object>();
								returnMap.put("eqt_code", eqt_code);
								returnMap.put("pre_tx_id", pretx_id);
								returnMap.put("money_amt", money);
								
								// 设备里面缺钱 应该也需要通知设备
								
								
								String returnData=JsonUtils.getJsonString4JavaPOJO(returnMap);
								String topic=MqPublisher.TOPIC_CF_OUTCASH;
								try {
									//mqttClient.publishMsgOne(topic, returnData, eqt_code);
									MqPublisher.publish(topic, returnMap, eqt_code);
									
								} catch (Throwable e) {
									
									//记录到日志 ，并且发邮件
									new EmailHelper().sendEmailToAdminInThread(String.format(" MQ publish exception .topic: %s,payload:%,to_client_id:%s, exception detail:%s", topic,returnData,eqt_code,e.toString()), e.toString());
									
								} 
								
							}
							
							
							
						 }
						
						
					}else if(out_item.getScriptPubKey().getScriptType()==Script.ScriptType.PUB_KEY){
						String pub_key=new String(out_item.getScriptPubKey().getPubKeyHash());
						logger.info(" pub key: "+pub_key);
					}
					
					
					
				} catch (Exception e) {
					System.err.println(e.toString());
				}
				

			}
			
			logger.info("");

		}
		
		
		@Override
		public synchronized void onCoinsReceived(final Wallet wallet, final Transaction transaction, Coin prevBalance, Coin newBalance) {
			final Coin value = transaction.getValueSentToMe(wallet);

			logger.info(" transaction info https://www.blocktrail.com/tBTC/tx/"+transaction.getHashAsString());
			
			logger.info("Received tx for " + value.toFriendlyString() + ": " + transaction);
			
			logger.info("Previous balance is " + prevBalance.toFriendlyString());

			logger.info("New estimated balance is " + newBalance.toFriendlyString());

			logger.info("Coin received, wallet balance is :" + wallet.getBalance());
			
			logger.info(" transaction fee:"+transaction.getFee().toFriendlyString());
			
			logger.info(String.format(" number of broadcast peers: %s , confidence type:%s, block height :%s, number of confirms:%s", 
					transaction.getConfidence().numBroadcastPeers()+"",
					transaction.getConfidence().getConfidenceType(),
					transaction.getConfidence().getAppearedAtChainHeight(),
					transaction.getConfidence().getDepthInBlocks()));
			
			
			Futures.addCallback(transaction.getConfidence().getDepthFuture(1), new FutureCallback<TransactionConfidence>() {
				public void onSuccess(TransactionConfidence result) {
					// Transaction confirmed
					logger.info("Transaction confirmed, wallet balance is :" + wallet.getBalance());
				}

				public void onFailure(Throwable t) {
					t.printStackTrace();
				}
			});
		}

	}

}
